<!doctype html>
<!--
@license
Copyright (c) 2015 The Polymer Project Authors. All rights reserved.
This code may only be used under the BSD style license found at http://polymer.github.io/LICENSE.txt
The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt
The complete set of contributors may be found at http://polymer.github.io/CONTRIBUTORS.txt
Code distributed by Google as part of the polymer project is also
subject to an additional IP rights grant found at http://polymer.github.io/PATENTS.txt
-->
<html>
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
  <title>iron-a11y-keys</title>

  <script src="../../webcomponentsjs/webcomponents-lite.js"></script>
  <script src="../../web-component-tester/browser.js"></script>
  <script src="../../iron-test-helpers/mock-interactions.js"></script>

  <link rel="import" href="../../polymer/polymer.html">
  <link rel="import" href="../iron-a11y-keys-behavior.html">
</head>
<body>
  <test-fixture id="BasicKeys">
    <template>
      <x-a11y-basic-keys></x-a11y-basic-keys>
    </template>
  </test-fixture>

  <test-fixture id="NonPropagatingKeys">
    <template>
      <x-a11y-basic-keys stop-keyboard-event-propagation></x-a11y-basic-keys>
    </template>
  </test-fixture>

  <test-fixture id="ComboKeys">
    <template>
      <x-a11y-combo-keys></x-a11y-combo-keys>
    </template>
  </test-fixture>

  <test-fixture id="AlternativeEventKeys">
    <template>
      <x-a11y-alternate-event-keys></x-a11y-alternate-event-keys>
    </template>
  </test-fixture>

  <test-fixture id="BehaviorKeys">
    <template>
      <x-a11y-behavior-keys></x-a11y-behavior-keys>
    </template>
  </test-fixture>

  <test-fixture id="PreventKeys">
    <template>
      <x-a11y-prevent-keys></x-a11y-prevent-keys>
    </template>
  </test-fixture>

  <script>
suite('Polymer.IronA11yKeysBehavior', function() {
  var keys;

  suiteSetup(function() {
    var KeysTestBehavior = [Polymer.IronA11yKeysBehavior, {
      properties: {
        keyCount: {
          type: Number,
          value: 0
        }
      },

      _keyHandler: function(event) {
        this.keyCount++;
        this.lastEvent = event;
      },

      // Same as _keyHandler, used to distinguish who's called before who.
      _keyHandler2: function(event) {
        this.keyCount++;
        this.lastEvent = event;
      },

      _preventDefaultHandler: function(event) {
        event.preventDefault();
        this.keyCount++;
        this.lastEvent = event;
      }
    }];

    Polymer({
      is: 'x-a11y-basic-keys',

      behaviors: [
        KeysTestBehavior
      ],

      keyBindings: {
        'space': '_keyHandler',
        '@': '_keyHandler',
        'esc': '_keyHandler'
      }
    });

    Polymer({
      is: 'x-a11y-combo-keys',

      behaviors: [
        KeysTestBehavior
      ],

      keyBindings: {
        'enter': '_keyHandler2',
        'ctrl+shift+a shift+enter': '_keyHandler'
      }
    });

    Polymer({
      is: 'x-a11y-alternate-event-keys',

      behaviors: [
        KeysTestBehavior
      ],

      keyBindings: {
        'space:keyup': '_keyHandler'
      }
    });

    var XA11yBehavior = {
      keyBindings: {
        'enter': '_keyHandler'
      }
    };

    Polymer({
      is: 'x-a11y-behavior-keys',

      behaviors: [
        KeysTestBehavior,
        XA11yBehavior
      ],

      keyBindings: {
        'enter': '_keyHandler'
      }
    });

    Polymer({
      is: 'x-a11y-prevent-keys',

      behaviors: [
        KeysTestBehavior,
        XA11yBehavior
      ],

      keyBindings: {
        'space a': '_keyHandler',
        'enter shift+a': '_preventDefaultHandler'
      }
    });
  });

  suite('basic keys', function() {
    setup(function() {
      keys = fixture('BasicKeys');
    });

    test('trigger the handler when the specified key is pressed', function() {
      MockInteractions.pressSpace(keys);

      expect(keys.keyCount).to.be.equal(1);
    });

    test('keyEventTarget can be null, and disables listeners', function() {
      keys.keyEventTarget = null;
      MockInteractions.pressSpace(keys);

      expect(keys.keyCount).to.be.equal(0);
    });

    test('trigger the handler when the specified key is pressed together with a modifier', function() {
      var event = new CustomEvent('keydown');
      event.ctrlKey = true;
      event.keyCode = event.code = 32;
      keys.dispatchEvent(event);
      expect(keys.keyCount).to.be.equal(1);
    });

    test('handles special character @', function() {
      MockInteractions.pressAndReleaseKeyOn(keys, undefined, [], '@');

      expect(keys.keyCount).to.be.equal(1);
    });

    test('handles variations of Esc key', function() {
      MockInteractions.pressAndReleaseKeyOn(keys, undefined, [], 'Esc');
      expect(keys.keyCount).to.be.equal(1);

      MockInteractions.pressAndReleaseKeyOn(keys, undefined, [], 'Escape');
      expect(keys.keyCount).to.be.equal(2);

      MockInteractions.pressAndReleaseKeyOn(keys, 27, [], '');
      expect(keys.keyCount).to.be.equal(3);
    });

    test('do not trigger the handler for non-specified keys', function() {
      MockInteractions.pressEnter(keys);

      expect(keys.keyCount).to.be.equal(0);
    });

    test('can have bindings added imperatively', function() {
      keys.addOwnKeyBinding('enter', '_keyHandler');

      MockInteractions.pressEnter(keys);
      expect(keys.keyCount).to.be.equal(1);

      MockInteractions.pressSpace(keys);
      expect(keys.keyCount).to.be.equal(2);
    });

    test('can remove imperatively added bindings', function() {
      keys.addOwnKeyBinding('enter', '_keyHandler');
      keys.removeOwnKeyBindings();

      MockInteractions.pressEnter(keys);
      expect(keys.keyCount).to.be.equal(0);

      MockInteractions.pressSpace(keys);
      expect(keys.keyCount).to.be.equal(1);
    });

    test('allows propagation beyond the key combo handler', function() {
      var keySpy = sinon.spy();
      document.addEventListener('keydown', keySpy);

      MockInteractions.pressEnter(keys);

      expect(keySpy.callCount).to.be.equal(1);
    });

    suite('edge cases', function() {
      test('knows that `spacebar` is the same as `space`', function() {
        var event = new CustomEvent('keydown');
        event.key = 'spacebar';
        expect(keys.keyboardEventMatchesKeys(event, 'space')).to.be.equal(true);
      });

      test('handles `+`', function() {
        var event = new CustomEvent('keydown');
        event.key = '+';
        expect(keys.keyboardEventMatchesKeys(event, '+')).to.be.equal(true);
      });

      test('handles `:`', function() {
        var event = new CustomEvent('keydown');
        event.key = ':';
        expect(keys.keyboardEventMatchesKeys(event, ':')).to.be.equal(true);
      });

      test('handles ` ` (space)', function() {
        var event = new CustomEvent('keydown');
        event.key = ' ';
        expect(keys.keyboardEventMatchesKeys(event, 'space')).to.be.equal(true);
      });
    });

    suite('matching keyboard events to keys', function() {
      test('can be done imperatively', function() {
        var event = new CustomEvent('keydown');
        event.keyCode = 65;
        expect(keys.keyboardEventMatchesKeys(event, 'a')).to.be.equal(true);
      });

      test('can be done with a provided keyboardEvent', function() {
        var event;
        MockInteractions.pressSpace(keys);
        event = keys.lastEvent;

        expect(event.detail.keyboardEvent).to.be.okay;
        expect(keys.keyboardEventMatchesKeys(event, 'space')).to.be.equal(true);
      });

      test('can handle variations in arrow key names', function() {
        var event = new CustomEvent('keydown');
        event.key = 'up';
        expect(keys.keyboardEventMatchesKeys(event, 'up')).to.be.equal(true);
        event.key = 'ArrowUp';
        expect(keys.keyboardEventMatchesKeys(event, 'up')).to.be.equal(true);
      });
    });

    suite('matching keyboard events to top row and number pad digit keys', function() {
      test('top row can be done imperatively', function() {
        var event = new CustomEvent('keydown');
        event.keyCode = 49;
        expect(keys.keyboardEventMatchesKeys(event, '1')).to.be.equal(true);
      });

      test('number pad digits can be done imperatively', function() {
        var event = new CustomEvent('keydown');
        event.keyCode = 97;
        expect(keys.keyboardEventMatchesKeys(event, '1')).to.be.equal(true);
      });
    });
  });

  suite('combo keys', function() {
    setup(function() {
      keys = fixture('ComboKeys');
    });

    test('trigger the handler when the combo is pressed', function() {
      var event = new CustomEvent('keydown');

      event.ctrlKey = true;
      event.shiftKey = true;
      event.keyCode = event.code = 65;

      keys.dispatchEvent(event);

      expect(keys.keyCount).to.be.equal(1);
    });

    test('check if KeyBoardEvent.key is alpha-numberic', function() {
      var event = new CustomEvent('keydown');

      event.ctrlKey = true;
      event.shiftKey = true;
      event.key = 'A';

      keys.dispatchEvent(event);

      expect(keys.keyCount).to.be.equal(1);
    });

    test('trigger also bindings without modifiers', function() {
      var event = new CustomEvent('keydown');
      // Combo `shift+enter`.
      event.shiftKey = true;
      event.keyCode = event.code = 13;
      keys.dispatchEvent(event);
      expect(keys.keyCount).to.be.equal(2);
    });

    test('give precendence to combos with modifiers', function() {
      var enterSpy = sinon.spy(keys, '_keyHandler2');
      var shiftEnterSpy = sinon.spy(keys, '_keyHandler');
      var event = new CustomEvent('keydown');
      // Combo `shift+enter`.
      event.shiftKey = true;
      event.keyCode = event.code = 13;
      keys.dispatchEvent(event);
      expect(enterSpy.called).to.be.true;
      expect(shiftEnterSpy.called).to.be.true;
      expect(enterSpy.calledAfter(shiftEnterSpy)).to.be.true;
    });

  });

  suite('alternative event keys', function() {
    setup(function() {
      keys = fixture('AlternativeEventKeys');
    });

    test('trigger on the specified alternative keyboard event', function() {
      MockInteractions.keyDownOn(keys, 32);

      expect(keys.keyCount).to.be.equal(0);

      MockInteractions.keyUpOn(keys, 32);

      expect(keys.keyCount).to.be.equal(1);
    });
  });

  suite('behavior keys', function() {
    setup(function() {
      keys = fixture('BehaviorKeys');
    });

    test('bindings in other behaviors are transitive', function() {
      MockInteractions.pressEnter(keys);
      expect(keys.keyCount).to.be.equal(2);
    });
  });

  suite('stopping propagation automatically', function() {
    setup(function() {
      keys = fixture('NonPropagatingKeys');
    });

    test('does not propagate key events beyond the combo handler', function() {
      var keySpy = sinon.spy();

      document.addEventListener('keydown', keySpy);

      MockInteractions.pressEnter(keys);

      expect(keySpy.callCount).to.be.equal(0);
    });
  });

  suite('prevent default behavior of event', function() {
    setup(function() {
      keys = fixture('PreventKeys');
    });

    test('`defaultPrevented` is correctly set', function() {
      MockInteractions.pressEnter(keys);
      expect(keys.lastEvent.defaultPrevented).to.be.equal(true);
    });

    test('only 1 handler is invoked', function() {
      var aSpy = sinon.spy(keys, '_keyHandler');
      var shiftASpy = sinon.spy(keys, '_preventDefaultHandler');
      var event = new CustomEvent('keydown', {
        cancelable: true
      });
      // Combo `shift+a`.
      event.shiftKey = true;
      event.keyCode = event.code = 65;
      keys.dispatchEvent(event);

      expect(keys.keyCount).to.be.equal(1);
      expect(shiftASpy.called).to.be.true;
      expect(aSpy.called).to.be.false;
    });
  });

  suite('remove key behavior with null target', function () {
    test('add and remove a iron-a11y-keys-behavior', function () {
      var element = document.createElement('x-a11y-basic-keys');
      element.keyEventTarget = null;
      document.body.appendChild(element);
      document.body.removeChild(element);
    });
  });

});
  </script>
</body>
</html>
